/*! \file
 * \brief Inline construction of propagator
 *
 * Propagator calculations
 */

#include "fermact.h"
#include "meas/inline/hadron_s/inline_propagator_s.h"
#include "meas/inline/abs_inline_measurement_factory.h"
#include "meas/glue/mesplq.h"
#include "util/ft/sftmom.h"
#include "util/info/proginfo.h"
#include "util/info/unique_id.h"
#include "actions/ferm/fermacts/fermact_factory_s.h"
#include "actions/ferm/fermacts/fermacts_aggregate_s.h"
#include "meas/inline/make_xml_file.h"

#include "meas/inline/io/named_objmap.h"

namespace Chroma 
{ 
  //! Propagator input
  void read(XMLReader& xml, const std::string& path, InlineStaggeredPropagatorEnv::Params::NamedObject_t& input)
  {
    XMLReader inputtop(xml, path);
    
    read(inputtop, "gauge_id", input.gauge_id);
    read(inputtop, "source_id", input.source_id);
    read(inputtop, "prop_id", input.prop_id);
  }

  //! Propagator output
  void write(XMLWriter& xml, const std::string& path, const InlineStaggeredPropagatorEnv::Params::NamedObject_t& input)
  {
    push(xml, path);

    write(xml, "gauge_id", input.gauge_id);
    write(xml, "source_id", input.source_id);
    write(xml, "prop_id", input.prop_id);

    pop(xml);
  }


  namespace InlineStaggeredPropagatorEnv 
  { 
    namespace
    {
      AbsInlineMeasurement* createMeasurement(XMLReader& xml_in, 
					      const std::string& path) 
      {
	return new InlineMeas(Params(xml_in, path));
      }

      //! Local registration flag
      bool registered = false;
    }

    const std::string name = "PROPAGATOR_STAG";

    //! Register all the factories
    bool registerAll() 
    {
      bool success = true; 
      if (! registered)
      {
	success &= StaggeredTypeFermActsEnv::registerAll();
	success &= TheInlineMeasurementFactory::Instance().registerObject(name, createMeasurement);
	registered = true;
      }
      return success;
    }


    // Param stuff
    Params::Params() { frequency = 0; }

    Params::Params(XMLReader& xml_in, const std::string& path) 
    {
      try 
      {
	XMLReader paramtop(xml_in, path);

	if (paramtop.count("Frequency") == 1)
	  read(paramtop, "Frequency", frequency);
	else
	  frequency = 1;

	// Parameters for source construction
	read(paramtop, "Param", param);

	// Read in the output propagator/source configuration info
	read(paramtop, "NamedObject", named_obj);

	// Possible alternate XML file pattern
	if (paramtop.count("xml_file") != 0) 
	{
	  read(paramtop, "xml_file", xml_file);
	}
      }
      catch(const std::string& e) 
      {
	QDPIO::cerr << __func__ << ": Caught Exception reading XML: " << e << std::endl;
	QDP_abort(1);
      }
    }


    void
    Params::writeXML(XMLWriter& xml_out, const std::string& path) 
    {
      push(xml_out, path);
    
      write(xml_out, "Param", param);
      write(xml_out, "NamedObject", named_obj);

      pop(xml_out);
    }


    // Function call
    void 
    InlineMeas::operator()(unsigned long update_no,
			   XMLWriter& xml_out) 
    {
      // If xml file not empty, then use alternate
      if (params.xml_file != "")
      {
	std::string xml_file = makeXMLFileName(params.xml_file, update_no);

	push(xml_out, "propagator_stag");
	write(xml_out, "update_no", update_no);
	write(xml_out, "xml_file", xml_file);
	pop(xml_out);

	XMLFileWriter xml(xml_file);
	func(update_no, xml);
      }
      else
      {
	func(update_no, xml_out);
      }
    }


    // Real work done here
    void 
    InlineMeas::func(unsigned long update_no,
		     XMLWriter& xml_out) 
    {
      START_CODE();

      StopWatch snoop;
      snoop.reset();
      snoop.start();

      // Test and grab a reference to the gauge field
      XMLBufferWriter gauge_xml;
      try
      {
	TheNamedObjMap::Instance().getData< multi1d<LatticeColorMatrix> >(params.named_obj.gauge_id);
	TheNamedObjMap::Instance().get(params.named_obj.gauge_id).getRecordXML(gauge_xml);
      }
      catch( std::bad_cast ) 
      {
	QDPIO::cerr << name << ": caught dynamic cast error" 
		    << std::endl;
	QDP_abort(1);
      }
      catch (const std::string& e) 
      {
	QDPIO::cerr << name << ": std::map call failed: " << e 
		    << std::endl;
	QDP_abort(1);
      }
      const multi1d<LatticeColorMatrix>& u = 
	TheNamedObjMap::Instance().getData< multi1d<LatticeColorMatrix> >(params.named_obj.gauge_id);

      push(xml_out, "propagator_stag");
      write(xml_out, "update_no", update_no);

      QDPIO::cout << name << ": propagator calculation" << std::endl;

      proginfo(xml_out);    // Print out basic program info

      // Write out the input
      params.writeXML(xml_out, "Input");

      // Write out the config header
      write(xml_out, "Config_info", gauge_xml);

      push(xml_out, "Output_version");
      write(xml_out, "out_version", 1);
      pop(xml_out);

      // Calculate some gauge invariant observables just for info.
      MesPlq(xml_out, "Observables", u);

      //
      // Read in the source along with relevant information.
      // 
      XMLReader source_file_xml, source_record_xml;

      int t0;
      int j_decay;
      bool make_sourceP = false;
      bool seqsourceP = false;
      QDPIO::cout << "Snarf the source from a named buffer" << std::endl;
      try
      {
	// Try the cast to see if this is a valid source
	LatticeStaggeredPropagator& source_tmp = 
	  TheNamedObjMap::Instance().getData<LatticeStaggeredPropagator>(params.named_obj.source_id);

	// Snarf the source info. This is will throw if the source_id is not there
	TheNamedObjMap::Instance().get(params.named_obj.source_id).getFileXML(source_file_xml);
	TheNamedObjMap::Instance().get(params.named_obj.source_id).getRecordXML(source_record_xml);

	// Try to invert this record XML into a source struct
	// First identify what kind of source might be here
	if (source_record_xml.count("/MakeSource") != 0)
	{
	  PropSourceConst_t source_header;

	  read(source_record_xml, "/MakeSource/PropSource", source_header);
	  j_decay = source_header.j_decay;
	  t0 = source_header.t_source;
	  make_sourceP = true;
	}
	else if (source_record_xml.count("/SequentialSource") != 0)
	{
	  ChromaProp_t      prop_header;
	  PropSourceConst_t source_header;
	  SeqSource_t       seqsource_header;

	  read(source_record_xml, "/SequentialSource/SeqSource", seqsource_header);
	  // Any source header will do for j_decay
	  read(source_record_xml, "/SequentialSource/ForwardProps/elem[1]/ForwardProp", 
	       prop_header);
	  read(source_record_xml, "/SequentialSource/ForwardProps/elem[1]/PropSource", 
	       source_header);
	  j_decay = source_header.j_decay;
	  t0 = seqsource_header.t_sink;
	  seqsourceP = true;
	}
	else
	{
	  throw std::string("No appropriate header found");
	}

	// Write out the source header
	write(xml_out, "Source_file_info", source_file_xml);
	write(xml_out, "Source_record_info", source_record_xml);
      }    
      catch (std::bad_cast)
      {
	QDPIO::cerr << name << ": caught dynamic cast error" 
		    << std::endl;
	QDP_abort(1);
      }
      catch (const std::string& e) 
      {
	QDPIO::cerr << name << ": error extracting source_header: " << e << std::endl;
	QDP_abort(1);
      }

      // Should be a valid cast now
      const LatticeStaggeredPropagator& quark_prop_source = 
	TheNamedObjMap::Instance().getData<LatticeStaggeredPropagator>(params.named_obj.source_id);
 
      QDPIO::cout << "Source successfully read and parsed" << std::endl;

      // Sanity check - write out the norm2 of the source in the Nd-1 direction
      // Use this for any possible verification
      {
	// Initialize the slow Fourier transform phases
	SftMom phases(0, true, Nd-1);

	multi1d<Double> source_corr = sumMulti(localNorm2(quark_prop_source), 
					       phases.getSet());

	push(xml_out, "Source_correlator");
	write(xml_out, "source_corr", source_corr);
	pop(xml_out);
      }

      //
      // Loop over the source color and spin, creating the source
      // and calling the relevant propagator routines. The QDP
      // terminology is that a propagator is a matrix in color
      // and spin space
      //
      try
      {
	TheNamedObjMap::Instance().create<LatticeStaggeredPropagator>(params.named_obj.prop_id);
      }
      catch (std::bad_cast)
      {
	QDPIO::cerr << name << ": caught dynamic cast error" 
		    << std::endl;
	QDP_abort(1);
      }
      catch (const std::string& e) 
      {
	QDPIO::cerr << name << ": error creating prop: " << e << std::endl;
	QDP_abort(1);
      }

      // Cast should be valid now
      LatticeStaggeredPropagator& quark_propagator = 
	TheNamedObjMap::Instance().getData<LatticeStaggeredPropagator>(params.named_obj.prop_id);
      int ncg_had = 0;

      //
      // Initialize fermion action
      //
      std::istringstream  xml_s(params.param.fermact.xml);
      XMLReader  fermacttop(xml_s);
      QDPIO::cout << "FermAct = " << params.param.fermact.id << std::endl;


      //
      // Try the factories
      //
      bool success = false;

      if (! success)
      {
	try
	{
	  StopWatch swatch;
	  swatch.reset();
	  QDPIO::cout << "Try the various factories" << std::endl;

	  // Typedefs to save typing
	  typedef LatticeStaggeredFermion      T;
	  typedef multi1d<LatticeColorMatrix>  P;
	  typedef multi1d<LatticeColorMatrix>  Q;

	  // Generic Wilson-Type stuff
	  Handle< StaggeredTypeFermAct<T,P,Q> >
	    S_f(TheStagTypeFermActFactory::Instance().createObject(params.param.fermact.id,
								   fermacttop,
								   params.param.fermact.path));


	  Handle< FermState<T,P,Q> > state(S_f->createState(u));

	  QDPIO::cout << "Suitable factory found: compute the quark prop" << std::endl;
	  swatch.start();
	  S_f->quarkProp(quark_propagator, 
			 xml_out, 
			 quark_prop_source,
			 state, 
			 params.param.invParam, 
			 params.param.quarkSpinType,
			 ncg_had);
	  swatch.stop();
	  QDPIO::cout << "Propagator computed: time= " 
		      << swatch.getTimeInSeconds() 
		      << " secs" << std::endl;
      
	  success = true;
	}
	catch (const std::string& e) 
	{
	  QDPIO::cout << name << ": caught exception around quarkprop: " << e << std::endl;
	}
      }


      if (! success)
      {
	QDPIO::cerr << "Error: no fermact found" << std::endl;
	QDP_abort(1);
      }


      push(xml_out,"Relaxation_Iterations");
      write(xml_out, "ncg_had", ncg_had);
      pop(xml_out);

      // Sanity check - write out the propagator (pion) correlator in the Nd-1 direction
      {
	// Initialize the slow Fourier transform phases
	SftMom phases(0, true, Nd-1);

	multi1d<Double> prop_corr = sumMulti(localNorm2(quark_propagator), 
					     phases.getSet());

	push(xml_out, "Prop_correlator");
	write(xml_out, "prop_corr", prop_corr);
	pop(xml_out);
      }


      // Save the propagator info
      try
      {
	QDPIO::cout << "Start writing propagator info" << std::endl;

	XMLBufferWriter file_xml;
	push(file_xml, "propagator");
	write(file_xml, "id", uniqueId());  // NOTE: new ID form
	pop(file_xml);

	XMLBufferWriter record_xml;
	if (make_sourceP)
	{
	  XMLReader xml_tmp(source_record_xml, "/MakeSource");

	  push(record_xml, "Propagator");
	  write(record_xml, "ForwardProp", params.param);
	  record_xml << xml_tmp;  // write out all the stuff under MakeSource
	  pop(record_xml);
	} 
	else if (seqsourceP)
	{
	  XMLReader xml_tmp(source_record_xml, "/SequentialSource");

	  push(record_xml, "SequentialProp");
	  write(record_xml, "SeqProp", params.param);
	  record_xml << xml_tmp;  // write out all the stuff under SequentialSource
	  pop(record_xml);
	}

	// Write the propagator xml info
	TheNamedObjMap::Instance().get(params.named_obj.prop_id).setFileXML(file_xml);
	TheNamedObjMap::Instance().get(params.named_obj.prop_id).setRecordXML(record_xml);

	QDPIO::cout << "Propagator successfully updated" << std::endl;
      }
      catch (std::bad_cast)
      {
	QDPIO::cerr << name << ": caught dynamic cast error" 
		    << std::endl;
	QDP_abort(1);
      }
      catch (const std::string& e) 
      {
	QDPIO::cerr << name << ": error extracting prop_header: " << e << std::endl;
	QDP_abort(1);
      }

      pop(xml_out);  // propagator

      snoop.stop();
      QDPIO::cout << name << ": total time = "
		  << snoop.getTimeInSeconds() 
		  << " secs" << std::endl;

      QDPIO::cout << name << ": ran successfully" << std::endl;

      END_CODE();
    } 

  }
}
